﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using TaskLib.UI;

namespace TaskLib.Scheduling
{
#nullable enable
    public class TaskSchedulerForPriority : TaskSchedulerBase
    {
        #region win32 api

        [DllImport("kernel32.dll")]
        private static extern UIntPtr SetThreadAffinityMask(IntPtr hThread, UIntPtr dwThreadAffinityMask);

        [DllImport("kernel32.dll")]
        private static extern IntPtr GetCurrentThread();

        #endregion

        /// <summary>
        /// 普通任务队列
        /// </summary>
        private readonly ConcurrentQueue<Task> m_normalTasks = new ConcurrentQueue<Task>();

        /// <summary>
        /// 紧急任务队列
        /// </summary>
        private readonly ConcurrentQueue<Task> m_urgentTasks = new ConcurrentQueue<Task>();

        /// <summary>
        /// 处理器数量
        /// </summary>
        private readonly int m_processorCount;

        /// <summary>
        /// 工作线程集合
        /// </summary>
        private readonly Thread[] m_workers;

        /// <summary>
        /// 工作标识
        /// </summary>
        private readonly bool m_isWorking;

        public TaskSchedulerForPriority(int urgentCoreCount = 1,
            int threadsPerCore = 2)
        {
            m_processorCount = Environment.ProcessorCount;
            if (threadsPerCore <= 0) throw new ArgumentOutOfRangeException("threadsPerCore <= 0");
            if (urgentCoreCount <= 0 || urgentCoreCount >= m_processorCount) throw new ArgumentOutOfRangeException("urgentCoreCount");

            m_isWorking = true;
            int workerCount = m_processorCount * threadsPerCore;

            m_workers = new Thread[workerCount];
            int normalThreadCount = (m_processorCount - urgentCoreCount) * threadsPerCore;
            int urgentThreadCount = workerCount - normalThreadCount;
            int normalCoreCount = m_processorCount - urgentCoreCount;

            // 分配线程的CPU使用
            int[] normalUsage = new int[normalCoreCount];
            int[] urgentUsage = new int[urgentCoreCount];
            for (int cpuid = 0; cpuid < m_processorCount; cpuid++)
            {
                if (cpuid < normalUsage.Length)
                {
                    normalUsage[cpuid] = cpuid;
                }
                else
                {
                    urgentUsage[cpuid - normalCoreCount] = cpuid;
                }
            }

            uint normalMask = GetCPUMask(normalUsage);
            uint urgentMask = GetCPUMask(urgentUsage);

            Tuple<uint, ConcurrentQueue<Task>> normalParams = new Tuple<uint, ConcurrentQueue<Task>>(normalMask, m_normalTasks);
            Tuple<uint, ConcurrentQueue<Task>> urgentParams = new Tuple<uint, ConcurrentQueue<Task>>(urgentMask, m_urgentTasks);

            for (int i = 0; i < workerCount; i++)
            {
                Thread worker = new Thread(TaskExecute);
                m_workers[i] = worker;
                worker.IsBackground = true;
                if (i >= normalThreadCount)
                {
                    //突发任务处理
                    worker.Start(urgentParams);
                }
                else
                {
                    //正常任务处理
                    worker.Start(normalParams);
                }
            }
        }

        /// <summary>
        /// 获取CPU使用掩码
        /// </summary>
        /// <param name="usage">[0,1,2..5]</param>
        /// <returns></returns>
        private uint GetCPUMask(int[] usage)
        {
            uint mask = 0x00;
            foreach(int cpuid in usage)
            {
                uint cpuMask = (uint)0x01 << cpuid;
                mask |= cpuMask;
            }
            return mask;
        }

        /// <summary>
        /// 任务执行
        /// </summary>
        /// <param name="obj"></param>
        private void TaskExecute(object? obj)
        {
            if (obj == null) throw new ArgumentNullException("obj");

            Tuple<uint, ConcurrentQueue<Task>> param = (Tuple<uint, ConcurrentQueue<Task>>)obj;
            uint mask = param.Item1;
            ConcurrentQueue<Task> tasks = param.Item2;
            _ = SetThreadAffinityMask(GetCurrentThread(), (UIntPtr)mask);

            try
            {
                while (m_isWorking)
                {
                    if (tasks.Count > 0
                        && tasks.TryDequeue(out var task))
                    {
                        if (task is CustomTask)
                        {
                            CustomTask customTask = (CustomTask)task;
                            customTask.StartTime = DateTime.Now;
                            if (m_regulator != null)
                            {
                                m_regulator.TaskStatusChanged(customTask);
                            }

                        }
                        base.TryExecuteTask(task);
                        if (task is CustomTask)
                        {
                            CustomTask customTask = (CustomTask)task;
                            customTask.CompletedTime = DateTime.Now;
                            if (m_regulator != null)
                            {
                                m_regulator.TaskStatusChanged(customTask);
                            }

                        }
                    }

                    Thread.Sleep(10);
                }
            }
            finally
            {

            }
        }

        protected sealed override void QueueTask(Task task)
        {
            if (task is CustomTask customTask)
            {
                switch (customTask.Priority)
                {
                    case TaskPriority.Urgent:
                        {
                            m_urgentTasks.Enqueue(task);
                            break;
                        }
                    default:
                        {
                            m_normalTasks.Enqueue(task);
                            break;
                        }
                }
            }
            else
            {
                m_normalTasks.Enqueue(task);
            }
        }

        protected override IEnumerable<Task> GetScheduledTasks()
        {
            Task[] normalTasks = m_normalTasks.ToArray();
            Task[] urgentTasks = m_urgentTasks.ToArray();
            Task[] totalTasks = new Task[normalTasks.Length + urgentTasks.Length];
            Array.Copy(normalTasks, 0, totalTasks, 0, normalTasks.Length);
            Array.Copy(urgentTasks, 0, totalTasks, normalTasks.Length, urgentTasks.Length);
            return totalTasks;
        }

        public sealed override int MaximumConcurrencyLevel { get { return base.MaximumConcurrencyLevel; } }

        protected sealed override bool TryDequeue(Task task)
        {
            if (task is CustomTask customTask)
            {
                switch (customTask.Priority)
                {
                    case TaskPriority.Urgent:
                        {
                            return m_urgentTasks.TryDequeue(out _);
                        }
                    default:
                        {
                            return m_normalTasks.TryDequeue(out _);
                        }
                }
            }
            else
            {
                return m_normalTasks.TryDequeue(out _);
            }
        }

        protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
        {
            if (taskWasPreviouslyQueued)
            {
                if (TryDequeue(task))
                {
                    return base.TryExecuteTask(task);
                }
                else
                {
                    return false;
                }
            }
            else
            {
                return base.TryExecuteTask(task);
            }
        }
    }
}
